package coevolution;

import genome.BG;
import genome.GGT;
import genome.Genotype;
import genome.IG;
import genome.CGP;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Random;
import java.util.StringTokenizer;

import tool.Coordinate;
import tool.CoordinateArray;
import tool.RepeatRandom;
import tool.Utility;

public class GGTGenerator {

	private static String EXPERIMENT_NAME = "GGTVEC";
	public static int MXGEN = 100;
	public static int GLENGTH = 30;
	private static int POP_SIZE = 10;
	public static int LAMBDA = 5;
	private static int MXARCGEN =100;
	public static int MXVAL = 2;
	public static int MXDEPTH = 5;

	public static int NRUNS = 500;
	private static double PARCHIVE = 0.00;
	public static int SAVE_INTERVAL = 1;
	public static int G = 0;

	public static int RUN = 0;
	public static String STARTPOINT = null;

	public static int NEVAL = 0;
	public static String SEARCHNAME = "default"; //name of search used for file handling
	public static Genotype REPRESENTATIONA;
	public static Genotype REPRESENTATIONB;
	public static ArrayList<Genotype> POP1;
	public static ArrayList<Genotype> POP2;

	private static long SEED = System.currentTimeMillis();
	public static Random R = new RepeatRandom(SEED,0,0);
	
	public static aggregateCounter sCounter = new aggregateCounter();
	
	
	public static class aggregateCounter {
		
		//This class is just to support accumulating frequency and age count across multiple runs
		private CoordinateArray states1 = new CoordinateArray();
		private CoordinateArray states2 = new CoordinateArray();
		
			public void updateCounts(double[][] coords, int age, int pop){
				//pop is 1 or 2
				CoordinateArray s; 
				if(pop==1){
					s = states1;
				}
				else {
					s = states2;
				}
				
				for(int i=0;i<coords.length;i++){
					Coordinate c=new Coordinate((int)coords[i][0], (int)coords[i][1],age);
					int cPos = s.indexOf(c);
					if(cPos==-1){
							s.add(c);
							
					}
					else {
							s.get(cPos).update(age);
						}
					}
				}
			public void printCounts(String path){
				System.out.println("Printing to: "+path);
				Utility.dumpArrayList(this.states1,path+1+".dat");
				Utility.dumpArrayList(this.states2,path+2+".dat");
			}
		}
	
	public static void clearFit(ArrayList<Genotype> p1, ArrayList<Genotype> p2){
		for(Genotype g1:p1){
			g1.RELATIVE_FITNESS=0.0;
		}

		for(Genotype g2:p2){
			g2.RELATIVE_FITNESS=0.0;
		}
	}

	private static void coevolveWithIPCA() {
		
		if(STARTPOINT==null){
			init();
			System.out.println("Initialising populations.");
		}

		System.out.println("Pop initialised.");
		IPCA.clear();

		int counter = 0;
		
		double arcInc = 1.0/MXARCGEN; //every time the archive is updated, increase the probability of using it by 1/MXARCGEN

		while(G<MXARCGEN && counter < MXGEN){
			
			
			clearFit(POP1,POP2);
			System.out.println("Starting to evaluate");
			evalFull(POP1,POP2);
			System.out.println("Evaluating");
			//G = IPCA.submit(POP1, POP2, G);
			
			POP1 = order(POP1);
			POP2 = order(POP2);
			
			Utility.printPop(POP1);
			Utility.printPop(POP2);
			
			if(counter%SAVE_INTERVAL==0){
				double[][] coords1 = getCoords(POP1);
				double[][] coords2 = getCoords(POP2);
				
				sCounter.updateCounts(coords1, counter/SAVE_INTERVAL, 1);
				sCounter.updateCounts(coords2, counter/SAVE_INTERVAL, 2);
				
				//printCoords("./coords/coordPOP1_"+counter/SAVE_INTERVAL+".dat",coords1);
				//printCoords("./coords/coordPOP2_"+counter/SAVE_INTERVAL+".dat",coords2);
				
			}
			counter = counter+1;
			
			//PARCHIVE = PARCHIVE + arcInc;
			
			POP1 = generate(POP1,IPCA.CSt);
			POP2 = generate(POP2,IPCA.TSt);
		}
		G = 0; NEVAL = 0;
	}

	private static void configureRepresentations() {
		configureRepresentations("GGT","GGT"); //default to CGP
	}

	private static void configureRepresentations(String repA, String repB) {
		double[] in = {0.0,0.0};

		if(repA.equals("CGP")){
			setREPRESENTATIONA(new CGP(in,GLENGTH));
		}

		if(repB.equals("CGP")){
			setREPRESENTATIONB(new CGP(in,GLENGTH));
		}
		
		if(repA.equals("GGT")){
			setREPRESENTATIONA(new GGT(in,GLENGTH,MXVAL,MXDEPTH));
		}

		if(repB.equals("GGT")){
			setREPRESENTATIONB(new GGT(in,GLENGTH,MXVAL,MXDEPTH));
		}

		if(repA.equals("BG")){
			setREPRESENTATIONA(new BG(GLENGTH));
		}

		if(repB.equals("BG")){
			setREPRESENTATIONB(new BG(GLENGTH));
		}
		if(repA.equals("IG")){
			setREPRESENTATIONA(new IG(GLENGTH));
			
		}

		if(repB.equals("IG")){
			setREPRESENTATIONB(new IG(GLENGTH));
		}		
		
	}

	public static void continueSearch(){
		System.out.println(STARTPOINT);
		printCurrentParameters();
		coevolveWithIPCA();
		printRunFinalState();
	}

	public static void evalFull(ArrayList<Genotype> p1, ArrayList<Genotype> p2){

		double[][] outcomes = getOutcomes(p1, p2);

		Iterator<Genotype> p1iterator = p1.iterator();

		for(int i=0;i<outcomes.length;i++){
			Genotype g1 = p1iterator.next();
			Iterator<Genotype> p2iterator = p2.iterator();
			for (int j = 0; j < outcomes.length; j++) {

				Genotype g2 = p2iterator.next();

				g1.RELATIVE_FITNESS = g1.RELATIVE_FITNESS+outcomes[i][j];
				g2.RELATIVE_FITNESS = g2.RELATIVE_FITNESS-outcomes[i][j];

			}
		}
	}

	//pairs two genomes off against each other, fitness based on score. This method recomputes the evaluation regardless of prior info
	public static double[] evaluate(Genotype g1, Genotype g2){
		
		double[] fit = new double[2];
		
		int[] v1 = g1.toVector();
		int[] v2 = g2.toVector();
		
		int[] mx2 = Utility.mx(v2);
		int mx1 = v1[mx2[1]];
		if(mx1 > mx2[0]){
			fit[0] = 1.0; fit[1] = -1.0;
		}
		else {
			fit[0] = -1.0; fit[1] = 1.0;
		}
	
		NEVAL = NEVAL +1;
		
		return fit;

	}

	public static ArrayList<Genotype> generate(ArrayList<Genotype> pOld, ArrayList<Genotype> archive){

		ArrayList<Genotype> pNew;

		pNew = rankGenerate(archive, pOld);

		return pNew;
	}

	public static Genotype getNewIndividual(int popNum){

		Genotype g = null;

		double[] in = {0.0,0.0};

		switch(popNum){
		case 1:

			if(REPRESENTATIONA instanceof GGT){
				g = new GGT(in,GLENGTH,MXVAL,MXDEPTH);
			}

			if(REPRESENTATIONA instanceof BG){
				g = new BG(GLENGTH);
			}
			
			if(REPRESENTATIONA instanceof IG){
				g = new IG(GLENGTH);
			}
			
			if(REPRESENTATIONA instanceof CGP){
				g = new CGP(in,GLENGTH);
			}
			
			break;

		case 2:

			if(REPRESENTATIONB instanceof GGT){
				g = new GGT(in,GLENGTH,MXVAL,MXDEPTH);
			}

			if(REPRESENTATIONB instanceof BG){
				g = new BG(GLENGTH);
			}
			
			if(REPRESENTATIONB instanceof IG){
				g = new IG(GLENGTH);
			}
			
			if(REPRESENTATIONA instanceof CGP){
				g = new CGP(in,GLENGTH);
			}
			break;
		}

		return g;
	}

	private static double[][] getOutcomes(ArrayList<Genotype> p1, ArrayList<Genotype> p2) {
		int nC = p1.size(); int nT = p2.size();

		double[][] outcomes = new double[nC][nT];

		int i=0; int j=0;
		for(Genotype g1 : p1){		
			for(Genotype g2 : p2){
				double[] fit = evaluate(g1,g2);
				outcomes[i][j] = fit[0];

				j = j+1;

			}

			j = 0;
			i = i+1;
		}
		return outcomes;
	}

	public static int getPOP_SIZE() {
		return POP_SIZE;
	}

	public static ArrayList<Genotype> getPop1() {
		return POP1;
	}

	public static ArrayList<Genotype> getPop2() {
		return POP2;
	}

	private static void init(){

		POP1 = new ArrayList<Genotype>();
		POP2 = new ArrayList<Genotype>();

		for(int i=0;i<POP_SIZE;i++){

			Genotype ginA = getNewIndividual(1);
			Genotype ginB = getNewIndividual(2);

			POP1.add(ginA);
			POP2.add(ginB);
		}
	}

	public static void load(String s) throws IOException{
		String name = s;
		//loads the present population states from files.
		File fParam = new File(name+".par");
		File fPop1 = new File(name+".pop1");
		File fPop2 = new File(name+".pop2");
		File fArc1 = new File(name+".arc1");
		File fArc2 = new File(name+".arc2");

		BufferedReader bParam = null; BufferedReader bPop1=null; BufferedReader bPop2=null; BufferedReader bConst1=null; BufferedReader bConst2=null;
		BufferedReader bArc1 = null; BufferedReader bArc2 = null; BufferedReader bConst3 = null; BufferedReader bConst4 = null;

		try {
			bParam = new BufferedReader(new FileReader(fParam));
			bPop1 = new BufferedReader(new FileReader(fPop1));
			bPop2 = new BufferedReader(new FileReader(fPop2));
			bArc1 = new BufferedReader(new FileReader(fArc1));
			bArc2 = new BufferedReader(new FileReader(fArc2));
		}
		catch(IOException e){
			System.out.println("Failed to open files: IO, "+G+"@"+RUN);
		}

		POP_SIZE = Integer.parseInt(bParam.readLine());
		MXARCGEN = Integer.parseInt(bParam.readLine());
		GLENGTH = Integer.parseInt(bParam.readLine());
		LAMBDA = Integer.parseInt(bParam.readLine());
		NRUNS = Integer.parseInt(bParam.readLine());
		SEED = Long.parseLong(bParam.readLine());
		G  = Integer.parseInt(bParam.readLine());
		RUN = Integer.parseInt(bParam.readLine());

		int iCount = Integer.parseInt(bParam.readLine());
		int dCount = Integer.parseInt(bParam.readLine());

		configureRepresentations(bParam.readLine(),bParam.readLine());

		POP1 = loadPop(1,POP1,bPop1,bConst1); POP2 = loadPop(2,POP2,bPop2,bConst2);
		IPCA.CSt = loadPop(1,IPCA.CSt,bArc1,bConst3); IPCA.TSt = loadPop(2,IPCA.TSt,bArc2,bConst4);

		R = new RepeatRandom(SEED,iCount,dCount); //random number generator reinitialised to avoid bias to count from loading.

		bParam.close(); bPop1.close(); bPop2.close();
	}

	public static ArrayList<Genotype> loadPop(int n, ArrayList<Genotype> pop,BufferedReader bPop, BufferedReader bConst) throws IOException{

		pop = new ArrayList<Genotype>();
		while(bPop.ready()){

			StringTokenizer stz = new StringTokenizer(bPop.readLine());
			ArrayList<Integer> gen = new ArrayList<Integer>();

			while(stz.hasMoreTokens()){
				Integer a = new Integer(Integer.parseInt(stz.nextToken()));
				gen.add(a);
			}

			stz = new StringTokenizer(bConst.readLine());
			ArrayList<Double> constants = new ArrayList<Double>();

			while(stz.hasMoreTokens()){
				Double a = new Double(Double.parseDouble(stz.nextToken()));
				constants.add(a);
			}

			System.out.println("");

			double[] in = {0.0,0.0};

			Genotype g = null;
			//bit to determine which type of genotype to create - there has to be an easier way of handling this!!!
			//hack for now - need to fix how representations are handled.
			if(n==1){
				if(REPRESENTATIONA instanceof GGT){

					g = new GGT(in,GLENGTH,MXVAL,MXDEPTH);	
				}

				if(REPRESENTATIONA instanceof BG){
					g = new BG(GLENGTH);
				}
				
				if(REPRESENTATIONA instanceof IG){
					g = new IG(GLENGTH);
				}
			}
			else {
				if(REPRESENTATIONB instanceof GGT){
					g = new GGT(in,GLENGTH,MXVAL,MXDEPTH);
				}
				
				if(REPRESENTATIONB instanceof BG){
					g = new BG(GLENGTH);
				}
				
				if(REPRESENTATIONB instanceof IG){
					g = new IG(GLENGTH);
				}
			}

			for(int i=0;i<g.size;i++){
				g.genome[i] = gen.get(i);
			}

			pop.add(g);
		}
		return pop;
	}

	public static void loadSearch(String s) throws IOException{

		if(s!=null){
			STARTPOINT = s;
			System.out.println("Loading populations and configuring using file "+s);
			load(STARTPOINT);
			SEARCHNAME = s.substring(0,3);
		}
		else {
			System.out.println("Invalid file: "+s);
			throw new IOException();
		}
		printCurrentParameters();
	}

	public static void main(String args[]){
				
			String s = EXPERIMENT_NAME;
			save(EXPERIMENT_NAME);
			for(int i=RUN;i<NRUNS;i++){
				String name = s+"r"+RUN;
				newSearch(name);
				RUN = RUN +1;
				PARCHIVE = 0.0;
			}
			
			sCounter.printCounts("./"+s);
	}

	public static void newSearch(String s){
		SEARCHNAME = s;
		configureRepresentations();
		continueSearch();
	}

	public static ArrayList<Genotype> order(ArrayList<Genotype> p){
		Genotype[] pArray = p.toArray(new Genotype[p.size()]);
		Arrays.sort(pArray);
		p = new ArrayList<Genotype>();
		for(int i=0; i<pArray.length;i++){p.add(pArray[i]);}
		return p;
	}

	public static void printCurrentParameters(){
		System.out.println("POP_SIZE: "+POP_SIZE);
		System.out.println("MXGEN: "+MXARCGEN);
		System.out.println("gLength: "+GLENGTH);
		System.out.println("MXVAL: "+MXVAL);
		System.out.println("LAMBDA: "+LAMBDA);
		System.out.println("NRUNS: "+NRUNS);
		System.out.println("G: "+G);
		System.out.println("run: "+RUN);
	}

	private static void printRunFinalState() {
		ArrayList<Genotype> finalCandidates = IPCA.CSt;
		ArrayList<Genotype> finalTests = IPCA.TSt;

		System.out.println("Final Candidates: ");
		Utility.printPop(finalCandidates);
		System.out.println("------------");
		
		System.out.println("Final Tests: ");
		Utility.printPop(finalTests);
		System.out.println("------------");
	}

	private static ArrayList<Genotype> rankGenerate(ArrayList<Genotype> archive, ArrayList<Genotype> pOld) {

		ArrayList<Genotype> pNew = Utility.copy(pOld);

		int j = 1;
		for(int i=0;i<pNew.size()-LAMBDA;i++){
			if(j>LAMBDA){j=1;}

			Genotype Gmutant = null;
			Genotype g = null;

			if(R.nextDouble()<PARCHIVE && archive.size()>0){ //If archive has members, PARCHIVE chance to select from archive instead.
				g = archive.get(R.nextInt(archive.size()));
			}
			else {
				g = pNew.get(pNew.size()-j);
			}

			Gmutant = g.stoMutate();
			//Gmutant = g.pointMutate();
			pNew.set(i, Gmutant);
			j++;
		}
		return pNew;
	}

	public static void save(String s){
		String name = s+"g"+G+"r"+RUN+"p"+PARCHIVE;
		//save the present population and archive states to file.
		File fParam = new File(name+".par");
		/*File fPop1 = new File(name+".pop1");
		File fPop2 = new File(name+".pop2");
		File fArc1 = new File(name+".arc1");
		File fArc2 = new File(name+".arc2");*/

		PrintWriter pParam = null; //PrintWriter pPop1=null; PrintWriter pPop2=null;
		//PrintWriter pArc1 = null; PrintWriter pArc2 = null;

		try {
			pParam = new PrintWriter(new FileWriter(fParam));
			/*pPop1 = new PrintWriter(new FileWriter(fPop1));
			pPop2 = new PrintWriter(new FileWriter(fPop2));
			pArc1 = new PrintWriter(new FileWriter(fArc1));
			pArc2 = new PrintWriter(new FileWriter(fArc2));*/

		}
		catch(IOException e){
			System.out.println("Save failed: IO, "+G+"@"+RUN); System.exit(0);
		}

		pParam.println(POP_SIZE);
		pParam.println(MXARCGEN);
		pParam.println(GLENGTH);
		pParam.println(LAMBDA);
		pParam.println(NRUNS);
		pParam.println(SEED);
		pParam.println(G);
		pParam.println(RUN);
		pParam.println(((RepeatRandom)R).getiCalls());
		pParam.println(((RepeatRandom)R).getdCalls());
		Utility.printRepType(pParam, REPRESENTATIONA);
		Utility.printRepType(pParam, REPRESENTATIONB);

		/*for(Genotype g : POP1){
			pPop1.println(g);
		}

		for(Genotype g : IPCA.CSt){
			pArc1.println(g);
		}

		for(Genotype g : POP2){
			pPop2.println(g);
		}

		for(Genotype g : IPCA.TSt){
			pArc2.println(g);
		}
*/
		pParam.close();
		/*pPop1.close();
		pPop2.close();
		pArc1.close();
		pArc2.close();*/
	}

	
	public static void setREPRESENTATIONA(Genotype rEPRESENTATIONA) {
		REPRESENTATIONA = rEPRESENTATIONA;
	}

	public static void setREPRESENTATIONB(Genotype rEPRESENTATIONB) {
		REPRESENTATIONB = rEPRESENTATIONB;
	}
	
	public static double[][] getCoords(ArrayList<Genotype> pop){
		double[][] coords = new double[pop.size()][CGP.fixedVector.length];
		int i=0;
		for(Genotype g:pop){
			int[] v = g.toVector();
			for(int j=0;j<v.length;j++){
				coords[i][j] = v[j];
			}
			i = i+1;
		}
		return coords;
	}

	public static void printCoords(String fileName,double[][] coords){
		Utility.writeMatToFile(fileName, coords);
	}

}
